package com.xy.iot.serial.port.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.xy.iot.common.utils.CommonUtil;
import com.xy.iot.serial.port.handler.NettyRxtxMessageDecoder;
import com.xy.iot.serial.port.service.RxtxClientService;
import com.xy.iot.serial.port.utils.SerialPortUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.rxtx.RxtxChannel;
import io.netty.channel.rxtx.RxtxChannelConfig;
import io.netty.channel.rxtx.RxtxDeviceAddress;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
@Slf4j
public class RxtxClientServiceImpl implements RxtxClientService {
    //通讯通道
    private  RxtxChannel channel;
    //工作线程组
    private  EventLoopGroup workerGroup;

    // 启动服务
    private  ChannelFuture future;
    private  Integer threadNum = null;

    @Override
    public void openComPort(String portName, int timeOut, int baudRate, int dataBits, int stopBits, int parity) {
        if (ObjectUtil.isNull(this.threadNum)){
            throw new RuntimeException("请先初始化线程数");
        }
        if (ObjectUtil.isNull(this.workerGroup)){
            throw new RuntimeException("请先初始化工作线程组");
        }
        SerialPortUtil.checkData(portName, timeOut, baudRate, dataBits, stopBits, parity);
        try {
            Bootstrap bootstrap  = new Bootstrap();
            bootstrap.group(this.workerGroup)
                    .channelFactory(new ChannelFactory<RxtxChannel>(){
                        @Override
                        public RxtxChannel newChannel() {
                            channel =  new RxtxChannel();
                            // 设置串口参数
                            setChannelConfig(channel.config(),timeOut, baudRate, dataBits, stopBits, parity);
                            return channel;
                        }
                    })
                    .handler(new ChannelInitializer<RxtxChannel>() {
                        @Override
                        protected void initChannel(RxtxChannel rxtxChannel) {
                            rxtxChannel.pipeline().addLast(
                                    // new NettyRxtxFrameDecoder(),
                                    new StringEncoder(StandardCharsets.UTF_8),
                                    new NettyRxtxMessageDecoder()
                            );
                        }
                    });
            this.future = bootstrap
                    // 调用bootstrap的connect方法尝试建立连接。
                    // 这里使用了假设的RxtxDeviceAddress对象，传入串口号portName作为目标地址，
                    // 模拟连接到一个串口设备的过程
                    .connect(new RxtxDeviceAddress(portName))
                    // 这个方法会使得当前线程阻塞，直到连接操作完成。
                    // 这意味着程序会在这里等待，直到成功建立连接到指定串口设备的通道（Channel）或者连接失败。
                    .sync()
                    .addListener(future -> {
                        if (future.isSuccess()) {
                            log.info("串口连接成功：{}", portName);
                        } else {
                            log.error("串口连接失败：{}",portName);
                        }
                    });
            // future
            //         //获取之前连接操作完成后的Channel实例，这是实际进行数据读写操作的通道。
            //         .channel()
            //         //调用Channel的closeFuture()方法获得一个ChannelFuture，这个Future代表了关闭该通道的操作结果。
            //         // 当调用Channel.close()时，这个Future将完成，表明通道已关闭。
            //         .closeFuture()
            //         //再次调用sync()方法，使得当前线程等待，直到通道关闭操作完成。
            //         //这保证了在关闭指令发出后，程序不会立即退出，而是会等待所有的资源释放和清理工作完成。
            //         .sync();
        }catch (Exception exception){
            log.error("打开串口失败", exception);
        }
    }

    @Override
    public void writeAndFlush(byte[] bytes) {
        if (ObjectUtil.isNull(this.channel)){
            throw new RuntimeException("请先打开串口");
        }
        if(!this.channel.isActive()
                || !this.channel.isOpen()
                || !this.channel.isWritable()){
            log.error("串口不活跃 或 不能写入");
            return;
        }
        ByteBuf buffer = this.channel.alloc().buffer();
        ByteBuf byteBuf = buffer.writeBytes(bytes);
        channel.writeAndFlush(byteBuf)
                .addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                log.info("指令下发成功：" + CommonUtil.decodeHexStr(new String(bytes)));
            } else {
                log.error("指令下发失败：" + CommonUtil.decodeHexStr(new String(bytes)));
            }
        });
    }

    @Override
    public void setChannelConfig(RxtxChannelConfig channelConfig, int timeOut, int baudRate, int dataBits, int stopBits, int parity) {
        channelConfig.setBaudrate(baudRate);
        channelConfig.setReadTimeout(timeOut);
        switch (dataBits){
            case 5:
                channelConfig.setDatabits(RxtxChannelConfig.Databits.DATABITS_5);
                break;
            case 6:
                channelConfig.setDatabits(RxtxChannelConfig.Databits.DATABITS_6);
                break;
            case 7:
                channelConfig.setDatabits(RxtxChannelConfig.Databits.DATABITS_7);
                break;
            case 8:
                channelConfig.setDatabits(RxtxChannelConfig.Databits.DATABITS_8);
                break;
            default:
                channelConfig.setDatabits(RxtxChannelConfig.Databits.DATABITS_8);
        }
        switch (stopBits){
            case 1:
                channelConfig.setStopbits(RxtxChannelConfig.Stopbits.STOPBITS_1);
                break;
            case 2:
                channelConfig.setStopbits(RxtxChannelConfig.Stopbits.STOPBITS_2);
                break;
            case 3:
                channelConfig.setStopbits(RxtxChannelConfig.Stopbits.STOPBITS_1_5);
                break;
            default:
                channelConfig.setStopbits(RxtxChannelConfig.Stopbits.STOPBITS_1);
        }
        switch (parity){
            case 0:
                channelConfig.setParitybit(RxtxChannelConfig.Paritybit.NONE);
                break;
            case 1:
                channelConfig.setParitybit(RxtxChannelConfig.Paritybit.ODD);
                break;
            case 2:
                channelConfig.setParitybit(RxtxChannelConfig.Paritybit.EVEN);
                break;
            case 3:
                channelConfig.setParitybit(RxtxChannelConfig.Paritybit.MARK);
                break;
            case 4:
                channelConfig.setParitybit(RxtxChannelConfig.Paritybit.SPACE);
                break;
            default:
                channelConfig.setParitybit(RxtxChannelConfig.Paritybit.NONE);
        }
    }

    @Override
    public void stop() {

        if (this.future != null){
            future.channel().close();
            log.info("关闭串口，future.channel().close()");
        }

        if (workerGroup != null){
            workerGroup.shutdownGracefully();
            log.info("关闭串口，工作线程池， workerGroup.shutdownGracefully()");
        }

        if (this.channel != null){
            channel.close();
            log.info("关闭串口， channel.close()");
        }
    }

    @Override
    public void init(int threadNum) {
        this.threadNum = threadNum;
        //串口使用阻塞io
        workerGroup = new OioEventLoopGroup(this.threadNum);
    }
}
